package cn.yunbamboos.rest.service.invoke;

import cn.hutool.core.io.FileUtil;
import cn.yunbamboos.rest.service.dto.in.BaseInDTO;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;

public class GenerateCreateInDTO {

    private static final Logger logger = LoggerFactory.getLogger(GenerateCreateInDTO.class);

    private final Class<? extends BaseInDTO> inDTOClass;
    private final boolean enableFile;
    private final Package pack;
    private final String packagePath;
    private final String className;

    public GenerateCreateInDTO(Class<? extends BaseInDTO> inDTOClass, boolean enableFile) {
        this.inDTOClass = inDTOClass;
        this.enableFile = enableFile;
        this.pack = inDTOClass.getPackage();
        this.packagePath = pack.getName().replace(".", "/");
        this.className = "Create" + inDTOClass.getSimpleName();
    }

    public byte[] generate() {
        String classType = packagePath + "/" + className;
        String dtoName = Type.getInternalName(inDTOClass);

        ClassWriter cw = new ClassWriter(0);
        MethodVisitor methodVisitor;

        cw.visit(Opcodes.V21, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, classType, null, "java/lang/Object", new String[]{"cn/yunbamboos/rest/service/ICreateInDTO"});

        cw.visitSource(className + ".java", null);

        {
            methodVisitor = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
            methodVisitor.visitCode();
            Label label0 = new Label();
            methodVisitor.visitLabel(label0);
            methodVisitor.visitLineNumber(8, label0);
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
            methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            methodVisitor.visitInsn(Opcodes.RETURN);
            Label label1 = new Label();
            methodVisitor.visitLabel(label1);
            methodVisitor.visitLocalVariable("this", "L"+classType+";", null, label0, label1, 0);
            methodVisitor.visitMaxs(1, 1);
            methodVisitor.visitEnd();
        }
        {
            methodVisitor = cw.visitMethod(Opcodes.ACC_PUBLIC, "create", "(Lcom/alibaba/fastjson2/JSONObject;)Lcn/yunbamboos/rest/service/dto/in/BaseInDTO;", null, null);
            methodVisitor.visitCode();
            Label label0 = new Label();
            methodVisitor.visitLabel(label0);
            methodVisitor.visitLineNumber(12, label0);
            methodVisitor.visitTypeInsn(Opcodes.NEW, dtoName);
            methodVisitor.visitInsn(Opcodes.DUP);
            methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, dtoName, "<init>", "()V", false);
            methodVisitor.visitVarInsn(Opcodes.ASTORE, 2);
            Label label1 = new Label();
            methodVisitor.visitLabel(label1);
            methodVisitor.visitLineNumber(13, label1);
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 2);
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
            methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, dtoName, "decode", "(Lcom/alibaba/fastjson2/JSONObject;)V", false);
            Label label2 = new Label();
            methodVisitor.visitLabel(label2);
            methodVisitor.visitLineNumber(14, label2);
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 2);
            methodVisitor.visitInsn(Opcodes.ARETURN);
            Label label3 = new Label();
            methodVisitor.visitLabel(label3);
            methodVisitor.visitLocalVariable("this", "Lcn/" + packagePath + "/" + className + ";", null, label0, label3, 0);
            methodVisitor.visitLocalVariable("json", "Lcom/alibaba/fastjson2/JSONObject;", null, label0, label3, 1);
            methodVisitor.visitLocalVariable("in", "L" + dtoName + ";", null, label1, label3, 2);
            methodVisitor.visitMaxs(2, 3);
            methodVisitor.visitEnd();
        }
        cw.visitEnd();

        byte[] bytes = cw.toByteArray();

        if (enableFile) {
            try {
                String fileDir = System.getProperty("user.dir") + File.separator + "target" + File.separator + pack.getName().replace(".", File.separator) + File.separator;
                FileUtil.mkdir(fileDir);
                String filePath = fileDir + File.separator + className + ".class";
                if (logger.isTraceEnabled()) {
                    logger.trace("write class file {}", filePath);
                }
                FileUtil.writeBytes(bytes, filePath);
            } catch (Exception e) {
                logger.error("write class file error", e);
            }
        }
        return bytes;
    }

    public String getPackagePath() {
        return packagePath;
    }

    public String getClassName() {
        return className;
    }
}
